| | | JNIWrapper TutorialVersion: 3.13
 Last Updated: February 3, 2025
 
 Copyright © 2002-2025 TeamDev Ltd.  Introduction
 Welcome to JNIWrapperTM for Microsoft Windows
    tutorial. It is designed to illustrate the creation of a simple
    application with certain features not provided by the Java platform and to
    demonstrate the basic concepts you need to know to develop successful
    applications using JNIWrapper. The program we are going to create will reveal JNIWrapper's
    capabilities through simple Win32 API functions. The first thing we'll do
    is to make some sounds using the system speaker. It's not very often that
    you hear Java programs are capable of such functionality, so this is the
    simplest way to make our application stand out. We'll call the program
    "Buzzer" to reflect the sound effect it produces and also add other nice
    features while going through the tutorial. The code will evolve as new concepts are introduced. By following
    the tutorial step by step and adding code, you will finally have a
    complete working program. The full Buzzer sample code is available for download
    from the JNIWrapper site.  Chapter 1. Set-up
 1.1. Creating the EnvironmentFirst things first. Let's set up the program working environment.
      Create a directory for the program and call it Sample. We will store our
      Java file, JNIWrapper library and any other resources here. It will also
      be our working directory for the sample application. In the working
      directory, let's create a directory for native files and call it
      bin. Now copy the Java library
      jniwrap-3.1.jar and
      winpack-3.0.jar to the Sample directory, the native
      DLL (jniwrap.dll) and license files
      (jniwrap.lic) to the
      Sample\bin directory. Create a Java application
      file in the Sample directory and call it
      Buzzer. You should now have the following structure:     Sample\
           bin\
               jniwrap.dll
               jniwrap.lic
           Buzzer.java
           jniwrap-3.1.jar
           winpack-3.0.jar 1.2. Creating the ApplicationNow let's set up the class of the application and run it before
      moving to the actual JNIWrapper coding. Put the following code into
      Buzzer.java:     import com.jniwrapper.*;
    public class Buzzer
    {
        public Buzzer()
        {
        }
        public static void main(String[] args)
        {
            System.out.println("The Buzzer is running");
            Buzzer buzzer = new Buzzer();
        }
    }Compile the class:     javac -classpath jniwrap-3.1.jar;winpack-3.0.jar Buzzer.java and run the application:     java -classpath jniwrap-3.1.jar;winpack-3.0;. Buzzer In this tutorial, compiling and running of the application should
      be understood as two actions being performed. Remember that you need to
      run the commands from the Sample directory for the
      program to work. OK, let's get to real work.  Chapter 2. Working with Native Libraries
 As was mentioned earlier, the first thing we'll try in our program
    is to make sounds using the system speaker. To do this, we'll use the
    standard Win32 Beep function. This function is
    exported from kernel32.dll, so our application
    requires this to be loaded. JNIWrapper provides a shortcut method for
    calling a function that loads a library and invokes its method. But the
    library itself provides a lot of interesting functions, so we'd better
    load it once to get access to the full variety of functions that we may
    choose to invoke.  2.1. Preparing a Search PathBefore doing any library loading, we need to prepare a search path
      for our libraries, namely JNIWrapper DLL and those we'll use for
      invoking functions. By default, libraries are located by the
      DefaultLibraryLoader singleton instance. This
      class searches for libraries following the path specified by the
      java.library.path system property; though, using
      its addPath method, we can add new directories
      there. All standard Win32 libraries that we will use are on the system
      PATH, which is automatically added to
      java.library.path. So we just need to add a search
      path for the JNIWrapper DLL. (We could copy it to some location on the
      system PATH, but it is generally considered not a good style, especially
      for deployed applications.) The DLL is in the bin
      folder and since we run only from the current directory, let's use the
      relative path to it. We'll use the relative paths later to keep the
      example simpler. So, in our main function, we'll add a line as shown
      below: DefaultLibraryLoader.getInstance().addPath("bin");You'll need to configure the library loader in all programs that
      use JNIWrapper before any native-related activity takes place.  2.2. Loading Native Code LibrariesLet's get back to our library. To load a library, you should
      create an object of the com.jniwrapper.Library type
      specifying the library name. Since kernel32.dll
      will be used often in our program, we'll create a field for it and load
      it to the constructor. Now we need to change our class in the following
      way (the new code is given in bold):     private Library _kernel;
    public Buzzer()
    {
        _kernel = new Library("kernel32");
    } There is no need to specify the extension: it is appended
      automatically. Libraries are loaded when their objects are constructed.
      You don't have to worry about freeing libraries: if a library object is
      no longer used, the native library defined by it is unloaded. Now let's
      compile and run the application to make sure everything is typed
      correctly and all the libraries are available.  Chapter 3. Using Simple Types
 In this chapter, we'll make beeping sounds by invoking a native
    function with simple parameters. Let's put our "beeping" code in a new
    method called buzz. As was mentioned earlier, we'll use the
    Beep function to produce sounds. This function
    takes two DWORD parameters and returns a BOOL
    value. We are not really interested in the return value and will ignore
    it, since this function is unlikely to fail. However, we need to pass
    values, so we'll create objects for the function parameters:     private void buzz()
    {
        // Prepare beep parameters: low and high frequencies and beep durations
            UInt32 low = new UInt32(400);
            UInt32 high = new UInt32(1000);
            UInt32 durLow = new UInt32(200);
            UInt32 durHigh = new UInt32(200);We used the standard JNIWrapper's UInt32 type which is
    exactly what DWORD is: a 32-bit unsigned integer. Our method
    will produce a series of lower and higher frequency beeps, that's why we
    define two sets of parameters: [low; durLow] and
    [high; durHigh]. In JNIWrapper, all numeric types, like Int,
    SingleFloat, and so on, have constructors that specify the
    initial value and also have getters and setters for the value. All
    parameters in JNIWrapper are mutable: they behave like variables. We could
    use one pair of parameters and modify their values between calls, but it's
    less illustrative, so let's have two sets.  Chapter 4. Invoking Functions
 Now that we have the library and parameters, one thing is still
    missing: the function to invoke. Functions are obtained from the library
    that exports them. To get a function, you should use the name it is
    exported with. Luckily, most Win32 API functions are exported with the
    same names (or almost the same - we will get to this case later) with
    which they appear in the documentation. To get the
    Beep function, let's use the following
    code:         // Obtain function reference from the library
        Function beep = _kernel.getFunction("Beep");Everything is ready to make beeping sounds. We'll create a small
    loop in which the Beep function is invoked to
    make high and low frequency beeps five times.         // Do the beeping
        for (int i = 0; i < 5; i++)
        {
        beep.invoke(null, high, durHigh);
        beep.invoke(null, low, durLow);
        }Note that the first argument of the invoke
    method is null. This is to indicate that we do not
    care about the return value. Later, we can easily add checking for it.
    Imagine the consequences of incorrectly ignoring the return value in the
    conventional JNI implementation: to get it later, you would need to modify
    the native method signature, change the native implementation and Java
    usages, and rebuild both Java and native code. This is just a simple
    example of how JNIWrapper can significantly save you development
    time. Our new method is complete. Let's invoke it from the main method to
    see how it works:     public static void main(String[] args)
    {
        // Add JNIWrap.dll directory to library loader search path
        DefaultLibraryLoader.getInstance().addPath("bin");
        Buzzer buzzer = new Buzzer();
        buzzer.buzz();
    }Compile and run the application. As a result, you should hear an
    alarm-like sound from the speaker.  Chapter 5. Using Strings
 Our next goal is to allow the user to run only one instance of the
    application at a time. Many Java programs try to achieve this by binding a
    particular TCP/IP socket. This approach does not ensure success because
    you cannot be absolutely sure the selected socket number is good. Our program will use a different method of creating a named mutex on
    start and checking for its existence to find out if another instance is
    running. With a well-chosen name, this method is practically bullet-proof.
    To create a mutex, we need to specify its name, which is a string.  5.1. String TypesIn Java, we have only one type of strings: the Unicode ones.
      Native side, however, has both single-byte (char*) and wide
      (wchar_t*) strings. JNIWrapper supports both types
      providing two concrete parameter classes:
      AnsiString for single-byte strings and
      WideString for wide strings. All strings we
      create in this application will consist only of 7-bit pure ASCII
      characters, so we'll use AnsiStrings. To make sure that our single-instance mechanism is working
      properly, we need to make our program keep running for as long as
      needed. One of the simplest ways to achieve this is to display a message
      box at the stage where we would like to wait. Of course, we could use
      JOptionPane here, but it's not in the spirit of this tutorial and
      besides, it does not produce that cool native notification sound. Let's
      prepare a method that would allow displaying information or error
      message box:     private void showMessageBox(String message, int flags) The first argument here is the message itself, whereas the second
      one is the flags mask for the MessageBox
      function we are going to invoke. Let's take a closer look at the
      MessageBox function. From the documentation, we
      can see that it has the following signature:     int MessageBox(
        HWND hWnd,          // handle to the owner window
        LPCTSTR lpText,     // text in the message box
        LPCTSTR lpCaption,  // message box title
        UINT uType          // message box style
    );  5.2. Creating Mutex and Displaying a MessageWe need to create a correct parameter set to invoke this function.
      The simplest way would be the UINT type as it has an
      equivalent type in JNIWrapper, i.e. UInt. As far as
      HWND is concerned, it is a handle and, therefore, a
      pointer. Generally speaking, pointers are more complex than the things
      we would like to touch upon in this chapter. Luckily, the data this
      pointer is pointing to will never be used, so we don't need to fill in
      this pointer's referenced area with any data. And last, but not least.
      In our case this pointer is simply NULL. On our
      platform, we could use a UInt32 pararameter since we know
      that all pointers are unsigned 32-bit integers, but a more correct way
      would be to use a Pointer.Void parameter. We'll set its
      handle value to zero. This corresponds to a C-like conversion from an
      integer. Whenever you need a constant such as
      (HWND)-1, use Pointer.Void specifying
      the value in the constructor or setter. The rest of the parameters are strings. The LPCTSTR
      type defines a pointer to a string that is ANSI or Unicode, depending on
      the build configuration. Behind the scenes, it means that there are two
      versions for this function for each of the string formats. In Win32 API
      their names are created from a function name by appending 'A' for the
      ANSI version or 'W' for Unicode. We would like to use ANSI, so our target function is spelled
      MessageBoxA. The arguments are string pointers,
      so in order to pass them, we create instances of
      AnsiString. Strings in C/C++ are pointers to
      string data. In JNIWrapper, string parameters actually represent string
      data which, to become string arguments, need to be pointed to. But when
      we are calling a function, its string parameters are automagically
      passed to the underlying function as a pointer to relieve programmers
      from the burden of creating any extra pointers. Therefore, the string
      parameters for the MessageBoxA function are
      defined just like AnsiString. Here's the argument preparation code for our method:         Pointer.Void hWnd = new Pointer.Void(0);
        AnsiString text = new AnsiString(message);
        AnsiString caption = new AnsiString("Buzzer");
        UInt uFlags = new UInt(flags);To invoke the MessageBoxA function, we
      need another library - user32.dll. Add the
      following code in bold to the declarations and constructor:     private Library _kernel;
    private Library _user;
    public Buzzer()
    {
        _kernel = new Library("kernel32");
        _user = new Library("user32");
        }Let's finish the showMessage
      method:         Function messageBox = _user.getFunction("MessageBoxA");
        messageBox.invoke(null, hWnd, text, caption, uFlags);Here, we also can safely ignore the return value. For further convenience, we'll create methods to display the error
      and information message boxes. The methods will specify the required
      style constants for those message box types.     private void message(String message)
    {
        showMessageBox(message, 0x30);
    }
    private void error(String message)
    {
        showMessageBox(message, 0x10);
    }  5.3. Passing Mutex Name ParameterWith what we have already learned, it is really easy to write a
      mutex-based single instance checking part. First, let's create a parameter representing a mutex name:     private boolean checkOneInstance()
    {
        AnsiString mutexName = new AnsiString("com.jniwrapper.sample.BuzzerMutex");Then, let's try to open a mutex with this name:         UInt32 desiredAcces = new UInt32(0x1F0001);
        Bool inheritHandle = new Bool(false);
        Pointer.Void mutexHandle = new Pointer.Void();
        Function openMutex = _kernel.getFunction("OpenMutexA");
        openMutex.invoke(mutexHandle, desiredAcces, inheritHandle, mutexName);Here, we are interested in the result. If there is no such mutex,
      the function returns NULL, otherwise we'll know
      that another instance is running and we need to close the new instance.
      To get the return value, we need to create a variable of the required
      type and pass it as the first argument to the
      invoke method. After the invocation is
      complete, the passed parameter contains the return value. Next, we need
      to test the return value:         if (!mutexHandle.isNull())
        {
            // Mutex exists - one instance is already running
            return false;
        }   5.4. Creating Locking MutexNow if the mutex doesn't exist, this instance is the first one and
      therefore we need to create a locking mutex. Creating is not much
      different from checking, except for the part where the
      NULL result now indicates the error we would like
      to catch. Here is the rest of the checking method:         // Not yet running - lock by creating mutex
        Pointer.Void mutexAttributes = new Pointer.Void(0);
        Bool initialOwner = new Bool(false);
        Function createMutex = _kernel.getFunction("CreateMutexA");
        createMutex.invoke(mutexHandle, mutexAttributes, initialOwner, mutexName);
        if (mutexHandle.isNull())
        {
            throw new RuntimeException("Mutex creation failed,
last error = " + getLastError(true));
        }
        return true;
    }  5.5. Error HandlingYou have probably noticed that the last piece of code references a
      method that is not yet defined: getLastError.
      It needs to return the last system error code, optionally clearing the
      error status depending on a boolean parameter. Let's implement it now.
      We already know all methods and types to write the following simple
      piece of code:     private long getLastError(boolean clear)
    {
        UInt32 r = new UInt32();
        Function getLE = _kernel.getFunction("GetLastError");
        getLE.invoke(r);
        long errCode = r.getValue();
        if (clear)
        {
            UInt32 zero = new UInt32(0);
            Function setLE = _kernel.getFunction("SetLastError");
            setLE.invoke(null, zero);
        }
        return errCode;
    }  5.6. Finishing IterationWe are ready to finish this iteration by adding the following code
      in bold to the main method:     public static void main(String[] args)
    {
        // Add JNIWrap.dll directory to library loader search path
        DefaultLibraryLoader.getInstance().addPath("bin");
        Buzzer buzzer = new Buzzer();
        if (!buzzer.checkOneInstance())
        {
            buzzer.error("Buzzer is already running");
            System.exit(0);
        }
        buzzer.message("Buzzer started, click OK to close");
    }Compile and run the application. As a result, you should see the
      "Buzzer started..." message box - don't close it and try to run another
      instance. Now you should see an error message box saying that the
      application is already running.  Chapter 6. Using Callbacks
 In this chapter, let's create a timer to make a sound after some
    time elapses. We'll use Windows native timers here, so we need a way to
    have the native code to call Java code. JNIWrapper provides such a
    capability using the com.jniwrapper.Callback class.
    You can create any number of callbacks that can have any parameters and
    return values. Creating a callback is simple: you need to subclass the
    Callback class, define the callback arguments and
    return value, and implement the callback method.
    Then, you pass an instance of this class as a parameter wherever you need
    a reference to the callback.  6.1. Creating a Timer CallbackWe are going to use the Windows API
      SetTimer function. It accepts the reference to
      the TimerProc callback function, so we need to
      implement such a callback. Here's its definition:     VOID CALLBACK TimerProc(
      HWND hwnd,         // handle to window
      UINT uMsg,         // WM_TIMER message
      UINT_PTR idEvent,  // timer identifier
      DWORD dwTime       // current system time
    );After examining the function signature, we can see we already know
      how to pass any of such types to a function call. Specifying them as
      callback arguments is no harder. Let's define our callback class as an
      inner class of our application class so that we can easily call the
      useful methods defined there:     private class TimeOutCallback extends Callback
    {
        private Pointer.Void _hwnd = new Pointer.Void();
        private UInt _msg = new UInt();
        private UInt _timerID = new UInt();
        private UInt32 _time = new UInt32();
        public TimeOutCallback()
        {We have defined the fields for each of our callback parameters.
      There is no return value, so we do not define any parameter for it. In
      the constructor, we need to configure our callback by specifying
      parameters of the callback signature:             init(new Parameter[] {
                _hwnd,
                _msg,
                _timerID,
                _time
            }, null);
        }The last null is the void return
      value. Now, to make this class complete and compilable, we'll implement
      the callback method. We'll make it just a buzz
      but will add more intelligent code to it later in this chapter:         public void callback()
        {
            buzz();
        }
    }The callback created is already usable. Easy, isn't it? Just
      imagine doing all this stuff using the conventional JNI
      techniques!  6.2. Using a Callback in the ApplicationNow we need to create a timer and pass the callback reference to
      be called when the timer elapses. To do this, we'll create a method and
      call it startTimer. This function requires passing a window handle. We
      will use the Wnd class that can obtain a native
      window handle from any Swing component. In subsequent iterations, we'll
      get our own window handle without messing up with internal
      implementation classes. For now, let's create a Java window and use its
      handle. Add the following code in bold to the class
      initialization: import com.jniwrapper.*;
import com.jniwrapper.win32.ui.Wnd;
import java.awt.*;
import sun.awt.windows.WToolkit;
import javax.swing.*;
public class Buzzer
{
    private Library _kernel;
    private Library _user;
    private Window _window;
    private static final int TIMER_ID = 1;
    public Buzzer()
    {
        _kernel = new Library("kernel32");
        _user = new Library("user32");
        _window = new JWindow();
        _window.setVisible(true);
    }Please take a look at the new constant: it will be used in the
      code below. The window will just provide its handle to hook the timer
      to. Let's implement the startTimer
      method:     private void startTimer(long timeout)
    {
        Wnd hWnd = new Wnd(_window);
        UInt eventID = new UInt(TIMER_ID);
        UInt timeOutVal = new UInt(timeout);
        UInt result = new UInt();
        TimeOutCallback timeOutCallback = new TimeOutCallback();
        Function setTimer = _user.getFunction("SetTimer");
        setTimer.invoke(result, hWnd, eventID, timeOutVal, timeOutCallback);Passing a callback is even more straightforward than its
      implementation: just create an object and pass it to the function that
      requires it. It's just like an event listener. Some sanity check and
      we're done:         if (result.getValue() == 0)
        {
            throw new RuntimeException("Failed to create a timer, error code = " + getLastError(true));
        }
    }Before moving on, let's implement the
      stopTimer method to be called from the
      callback:     private void stopTimer(Pointer.Void hwnd, UInt timerID)
    {
        Function killTimer = _user.getFunction("KillTimer");
        killTimer.invoke(null, hwnd, timerID);
    }Here we used JNIWrapper types as parameters, because we already
      have them in the callback and there is no need to unwrap the values just
      to wrap them back.  6.3. Testing ResultsLet's test the resulting code. Modify the main method in the
      following way:     public static void main(String[] args)
    {
        // Add jniwrap.dll directory to library loader search path
        DefaultLibraryLoader.getInstance().addPath("bin");
        Buzzer buzzer = new Buzzer();
        if (!buzzer.checkOneInstance())
        {
            buzzer.error("Buzzer is already running");
            System.exit(0);
        }
        buzzer.startTimer(5000);
    }Compile the program. If it is already running, there is no control
      to terminate it. To close the program, just press
      Ctrl+C in its console (remember not to use
      javaw.exe here or else you'll have to use the Task
      Manager to stop the buzzing). Run the program. You should hear a regular
      beeping at 5-second intervals until the program is terminated. This
      means that the timer we are using is repetitive, so we'll need to stop
      it when it is no longer needed.  6.4. Improving Callback CodeNow we'll complete the callback code with a few useful lines. We
      may want to: Here's the whole implementation of the
      callback method that does it all:         public void callback()
        {
            stopTimer(_hwnd, _timerID);
            buzz();
            message("Timer has elapsed!");
            System.exit(0);
        }Note that although our main thread terminates before the timer
      elapses, the program still lives because of non-daemon AWT threads that
      keep our window alive. Take a look at the first line of the function. When the callback
      is invoked, the variables specified at its creation are set to the
      argument values before the callback method is
      called. We pass two of the arguments to the
      stopTimer function. Our callback does not have
      a return value; if it had, we would need to assign the return value
      parameter before leaving the callback method.
      There are no limitations as to what things a callback can do other than
      those specified for the original callback. Compile and run the application. Five seconds after the start, the
      program will produce the usual sound, and then display the information
      message box saying that the timer has elapsed. Closing this message box
      will terminate the program.  Chapter 7. Using Structures
 In the previous chapter, we used an implementation-specific class to
    get the window handle. In this chapter, we are going to get one in a more
    legitimate way. We'll create a native window and later make it
    custom-shaped like a splash screen. Creating and managing a window using Windows API requires passing
    more complex parameters than we have used before, namely structures and
    pointers. We'll begin with structures. Any window has its event queue and requires some thread to dispatch
    its events. Messages are placed into the message queue in the form of the
    MSG structures. Let's examine the contents of this structure and implement
    it using JNIWrapper. Here is its definition: typedef struct tagMSG {
  HWND   hwnd;
  UINT   message;
  WPARAM wParam;
  LPARAM lParam;
  DWORD  time;
  POINT  pt;
} MSG, *PMSG;One of the MSG structure members is a structure itself:
    POINT pt. Its layout is as follows: typedef struct tagPOINT {
  LONG x;
  LONG y;
} POINT, *PPOINT;This one is really simple - it looks like a good starting point to
    learn how to create structures in JNIWrapper. Structures can be
    implemented by creating an instance of the
    com.jniwrapper.Structure class and initilazing them
    with objects representing fields. Most of them, however, are often reused
    and so it is usually better (and more readable) to create a subclass for
    each structure required. We'll choose the second approach:     private static class POINT extends Structure
    {
        public LongInt _x = new LongInt();
        public LongInt _y = new LongInt();First, we created a variable for each structure member. These
    variables are used for holding actual member data and for accessing it.
    Next, the structure must be initialized by defining its layout. Members
    are passed to the init method in the order they
    appear in the native-side structure declaration. Optionally, the structure
    alignment can be specified, but it is not needed here since all Windows
    API structures have the default alignment (of 1). Here is the rest of the
    code for this structure:         public POINT()
        {
            init(new Parameter[] {_x, _y});
        }
    }The POINT structure is ready. Using the same principle,
    let's create a class for the MSG structure:     private static class MSG extends Structure
    {
        Pointer.Void _hwnd = new Pointer.Void();
        UInt _message = new UInt();
        UInt32 _wParam = new UInt32();
        UInt32 _lParam = new UInt32();
        UInt32 _time = new UInt32();
        POINT _pt = new POINT();
        public MSG()
        {
            init(new Parameter[]{_hwnd, _message, _wParam, _lParam, _time, _pt});
        }
    }Note that defining a member of some complex type (structure) is no
    different from defining any member of simple ones. Uniformity is one of
    the JNIWrapper's main design goals.  Chapter 8. Using Pointers
 To demonstrate the use of a pointer in JNIWrapper, let's implement
    the event loop for our program. We'll do this before creating a window and
    doing all other preparation stuff, because using a pointer here is the
    simplest and most straightforward way. In fact, the only missing part will
    be the window handle, so let's assume that it is already stored in the
    _hSplash variable defined in the following way:     private Pointer.Void _hSplash; Add the above line to the declaration section of the class
    file.  8.1. Creating a Window Message LoopWe'll implement the message loop in the
      run method of our program. The loop consists of
      sequential invocation of three API functions:
      GetMessage, TranslateMessage
      and DispatchMessage. However, instead of taking the
      MSG structure as their argument, they all require
      LPMSG, i.e. a pointer to that structure. In JNIWrapper, pointers are similar to any other type. To create a
      pointer, you need to create an instance of the
      com.jniwrapper.Pointer class. Each pointer should
      point to some other parameter called a referenced object. Whenever a
      pointer is written or read, its referenced object is also written or
      read, respectively. Let's create a pointer to the MSG structure:     private void run()
    {
        Function getMessage = _user.getFunction("GetMessageA", null);
        Function translateMessage = _user.getFunction("TranslateMessage", null);
        Function dispatchMessage = _user.getFunction("DispatchMessageA", null);
        MSG msg = new MSG();
        Pointer msgPointer = new Pointer(msg);The code related to the pointer creation is marked bold. In this
      example, msgPointer is a pointer and
      msg is its referenced object. In the previous code we
      used the Pointer.Void class instead of pointers,
      because we did not care for the referenced object and did not need to
      provide one. In this case, we need to provide the MSG structure so we
      need to use the real pointer. The rest of this function code uses the
      created pointer just as any other parameter:         Bool result = new Bool();
        for (;;)
        {
            getMessage.invoke(result, msgPointer, _hSplash, new UInt32(0),
                               new UInt32(0));
            if (!result.getValue())
            {
                break;
            }
            translateMessage.invoke(null, msgPointer);
            dispatchMessage.invoke(null, msgPointer);
        }
    }Although there is no need for us to access or modify data in the
      msg structure, we could easily do so.  8.2. Pointers and StringsNow that we learned all the techniques, we can go into the gory
      details of creating a window. First off, we will define the most complex
      structure in this example: the equivalent of Windows API WNDCLASS
      structure. We have already seen that any type can become a structure
      member equally easily. This structure will be another example: it will
      hold a callback reference. Add this class to your code:     private static class WndClass extends Structure
    {
        private UInt32 _style = new UInt32();
        private Callback _lpfnWndProc;
        private Int32 _cbClsExtra = new Int32();
        private Int32 _cbWndExtra = new Int32();
        private Pointer.Void _hInstance = new Pointer.Void();
        private Pointer.Void _hIcon = new Pointer.Void();
        private Pointer.Void _hCursor = new Pointer.Void();
        private Pointer.Void _hbrBackground = new Pointer.Void();
        private AnsiString _lpszClassName = new AnsiString();
        public WndClass(Callback windowProc, String className,
                        Pointer.Void bgBrushHandle)
        {
            _style.setValue(3); // CS_HREDRAW | CS_VREDRAW
            _lpfnWndProc = windowProc;
            _lpszClassName.setValue(className);
            _cbClsExtra.setValue(0);
            _cbWndExtra.setValue(0);
            _hInstance.setValue(0);
            _hIcon.setValue(0);
            _hCursor.setValue(0);
            _hbrBackground.setValue(bgBrushHandle.getValue());
            init(new Parameter[] {_style, _lpfnWndProc, _cbClsExtra,
                 _cbWndExtra, _hInstance, _hIcon, _hCursor,
                 _hbrBackground, new Pointer.Void(0),
                 new Pointer(_lpszClassName)});
        }
    }  8.3. Using String Values in StructuresTake a look at the initialization of the
      _lpszClassName field: init(new Parameter[] {... new Pointer(_lpszClassName)});When defining structures that contain strings, you should always
      remember that there can be two types of them: pointers to character data
      and character arrays. To distinguish between the two, look at the member
      definition. Ones defined as char *name are pointers
      and others defined as char name[20] are arrays. In
      function calls, strings are always passed as pointers, so string
      parameters are automatically converted. But in structures, there is no
      way to know, so you should explicitly specify how the string is stored.
      If it is a pointer, a Pointer instance must be
      passed as the corresponding structure member. If it is an array, you
      should create a string with the maximum length equal to that of the
      expected character array and pass that parameter itself as the structure
      member. In this case, we have a pointer to characters version.  Chapter 9. Final Touch
 Now we are ready to finish the application. To do this, we need to
    implement a window procedure callback, register a window class, create a
    window, shape and center it, and define a window painting method. There is
    nothing special in any of these tasks so you can just take a look at the
    final code provided with this tutorial. After running it, you should see a
    small round window (on top of all other windows), in ten seconds the
    computer should make a beeping sound and pop up a message box saying that
    the timer has elapsed. Of course, the single instance rule is still
    enforced. The tutorial is over. By now you learned everything you need to
    start successfully using JNIWrapper in your applications. | 
 |  | Copyright © 2002-2021 TeamDev Ltd. |  | 
 | 
 |